package base

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/tealeg/xlsx"
	"gorm.io/gorm/schema"
)

//ColumnHeader 列头
type ColumnHeader struct {
	Name     string
	Caption  string
	Width    int
	Children []ColumnHeader
}

//Xlsx Xlsx实体类
type Xlsx struct {
	dao           Dao
	whereCauses   []WhereCause
	orderItems    []OrderItem
	columnHeaders []ColumnHeader
	leafs         []ColumnHeader
	gormSchema    *schema.Schema
}

//NewXlsx 新建excel导出
func NewXlsx(dao Dao, whereCauses []WhereCause, orderItems []OrderItem, columnHeaders []ColumnHeader) (xlsxObj *Xlsx) {
	xlsxObj = &Xlsx{
		dao:           dao,
		whereCauses:   whereCauses,
		orderItems:    orderItems,
		columnHeaders: columnHeaders,
	}
	xlsxObj.gormSchema, _ = schema.Parse(dao.GetEntity(), AppConf.cacheSchema, schema.NamingStrategy{})
	return
}

//Export 导出数据
func (x *Xlsx) Export(userID int, pTypes ...string) (*bytes.Buffer, error) {
	var err error
	var sheet *xlsx.Sheet
	file := xlsx.NewFile()
	sheet, err = file.AddSheet("Sheet1")
	if err != nil {
		return nil, err
	}
	x.createHeader(sheet)
	list, err := x.dao.Find(x.whereCauses, x.orderItems, 0, 0, userID, pTypes...)
	if err != nil {
		return nil, err
	}
	datas := reflect.ValueOf(list.Objectlist).Elem()
	for i := 0; i < datas.Len(); i++ {
		row := sheet.AddRow()
		for _, ch := range x.leafs {
			cell := row.AddCell()
			style := xlsx.Style{
				Border: *xlsx.NewBorder("thin", "thin", "thin", "thin"),
				Font:   *xlsx.NewFont(12, "宋体"),
			}
			style.Alignment.Horizontal = "left"
			style.Alignment.Vertical = "center"
			style.Font.Bold = false
			names := strings.Split(ch.Name, ".")
			index := -1
			for k, f := range x.gormSchema.Fields {
				if strings.EqualFold(f.Name, names[0]) {
					index = k
					break
				}
			}
			if index == -1 {
				return nil, fmt.Errorf("字段%s在实体类%s中不存在！", names[0], reflect.TypeOf(x.dao.GetEntity()))
			}
			field := datas.Index(i).FieldByIndex([]int{index})
			var val interface{}
			if l := len(names); l == 1 {
				if k := field.Kind(); k == reflect.Ptr {
					if field.IsNil() {
						val = nil
					} else {
						val = field.Elem().Interface()
					}
				} else {
					if field.IsValid() {
						val = field.Interface()
					}
				}
			} else if l == 2 {
				var obj interface{}
				if k := field.Kind(); k == reflect.Ptr {
					if !field.IsNil() {
						obj = field.Elem().Interface()
					}
				} else {
					if field.IsValid() {
						obj = field.Interface()
					}
				}
				if obj != nil {
					refObj := reflect.ValueOf(obj)
					field := refObj.FieldByName(names[1])
					if k := field.Kind(); k == reflect.Ptr {
						if field.IsNil() {
							val = nil
						} else {
							val = field.Elem().Interface()
						}
					} else {
						if field.IsValid() {
							val = field.Interface()
						}
					}
				}
			}
			if val == nil {
				cell.SetValue("")
			} else {
				date, ok := val.(time.Time)
				if ok {
					val = date.Format("2006-01-02")
					if date.IsZero() {
						val = ""
					}
				}
				cell.SetValue(val)
			}
			cell.SetStyle(&style)
		}
	}
	buff := new(bytes.Buffer)
	file.Write(buff)
	return buff, nil
}
func (x *Xlsx) createHeader(sheet *xlsx.Sheet) {
	maxDeep := 0
	for _, col := range x.columnHeaders {
		deep := x.getColumnDeep(&col)
		if deep > maxDeep {
			maxDeep = deep
		}
	}
	for i := 0; i < maxDeep; i++ {
		sheet.AddRow()
	}
	for _, col := range x.columnHeaders {
		x.exportColumn(sheet, &col, 1, maxDeep)
	}
	leafs := make([]ColumnHeader, 0, 20)
	for _, col := range x.columnHeaders {
		lfs := x.getColumnleafs(&col)
		leafs = append(leafs, lfs...)
	}
	x.leafs = leafs
}
func (x *Xlsx) getNumFmt(num interface{}) string {
	switch num.(type) {
	case float64, float32:
		return "#,##0.00 ;[red](#,##0).00"
	case int64, int32:
		return "#,##0 ;[red](#,##0)"
	default:
		return ""
	}
}

func (x *Xlsx) exportColumn(sheet *xlsx.Sheet, column *ColumnHeader, level, maxDeep int) {
	row := sheet.Rows[level-1]
	cell := row.AddCell()
	rowSpan := x.getColumnCount(column) - 1
	for i := 0; i < rowSpan; i++ {
		row.AddCell()
	}
	colSpan := 0
	cols := column.Children
	hasChildren := true
	if len(cols) == 0 {
		colSpan = maxDeep - level
		hasChildren = false
	}
	for i := 0; i < colSpan; i++ {
		row := sheet.Rows[level+i]
		row.AddCell()
	}
	cell.Merge(rowSpan, colSpan)
	cell.SetValue(column.Caption)
	style := xlsx.Style{
		Border: *xlsx.NewBorder("thin", "thin", "thin", "thin"),
		Font:   *xlsx.NewFont(14, "宋体"),
	}
	style.Alignment.Horizontal = "center"
	style.Alignment.Vertical = "center"
	style.Font.Bold = true
	cell.SetStyle(&style)
	if hasChildren {
		level++
		for _, ccol := range cols {
			x.exportColumn(sheet, &ccol, level, maxDeep)
		}
	}
}

func (x *Xlsx) getColumnleafs(column *ColumnHeader) []ColumnHeader {
	leafs := make([]ColumnHeader, 0, 5)
	cols := column.Children
	if len(cols) == 0 {
		leafs = append(leafs, *column)
	}
	for _, ccol := range cols {
		l2 := x.getColumnleafs(&ccol)
		leafs = append(leafs, l2...)
	}
	return leafs
}

func (x *Xlsx) getColumnCount(column *ColumnHeader) int {
	count := 0
	cols := column.Children
	if len(cols) == 0 {
		return 1
	}
	for _, ccol := range cols {
		c2 := x.getColumnCount(&ccol)
		count += c2
	}
	return count
}
func (x *Xlsx) getColumnDeep(column *ColumnHeader) int {
	cols := column.Children
	if len(cols) == 0 {
		return 1
	}
	deep := 0
	for _, col := range cols {
		d2 := x.getColumnDeep(&col)
		if d2 > deep {
			deep = d2
		}
	}
	return deep + 1
}
